﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

namespace WorkNodeFramework
{
    public class NodeReader<T> : IEnumerable<T>
        where T : WorkNode
    {
        WorkPart workPart;
        Predicate<T>[] predicates;
        int startIndex = 0;
        int stopIndex = int.MaxValue - 1;
        Select select;

        internal NodeReader(Select select, WorkPart workPart)
            : this(select, workPart, null)
        { }

        internal NodeReader(Select select, WorkPart workPart,
            int pageIndex, int pageCount)
            : this(select, workPart, pageIndex, pageCount,null)
        {
        }

        internal NodeReader(Select select, WorkPart workPart,
            Predicate<T>[] predicates)
        {
            this.select = select;
            this.workPart = workPart;
            if (predicates != null && predicates.Length > 0)
                this.predicates = predicates;
            LoadTables();
        }

        internal NodeReader(Select select, WorkPart workPart,
            int pageIndex, int pageCount,
            Predicate<T>[] predicates)
            : this(select, workPart, predicates)
        {
            this.startIndex = pageIndex * pageCount;
            this.stopIndex = (pageIndex + 1) * pageCount - 1;
        }

        void LoadTables()
        {
            select.Provider.GetTable<WorkNodeTable>(workPart.WorkNode);
            select.Provider.GetTable<WorkNodePropertiesTable>(workPart.Properties);
            select.Provider.GetTable<WorkNodeLangPropertiesTable>(workPart.LangProperties);
        }

        NodeEnumerator<T> enumerator;
        NodeEnumerator<T> GetWorkNodeEnumerator()
        {
            if (enumerator == null)
            {
                enumerator = new NodeEnumerator<T>(select, select.Provider, workPart, predicates, null, startIndex, stopIndex);
            }
            return enumerator;
        }

        #region IEnumerable<WorkNode> Members

        public IEnumerator<T> GetEnumerator()
        {
            NodeEnumerator<T> en = GetWorkNodeEnumerator();
            if (en.IsClosed)
                return en.Nodes.GetEnumerator();
            return en;
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetWorkNodeEnumerator();
        }

        #endregion

        public void Sort(params Comparison<T>[] orders)
        {
            if (orders.Length == 0)
                return;
            enumerator = new NodeEnumerator<T>(select, select.Provider, workPart, startIndex, stopIndex);
            if (orders.Length > 0)
                enumerator.Comparisons = orders;
            enumerator.Predicates = predicates;
            while (enumerator.MoveNext()) ;
        }

        public List<T> ToList()
        {
            NodeEnumerator<T> en = GetWorkNodeEnumerator();
            while (en.MoveNext()) ;
            return en.Nodes;
        }
    }

    public class NodeEnumerator<T> : IEnumerator<T>
        where T : WorkNode
    {
        IDataReader reader;
        Select select;
        DataProvider provider;
        WorkPart workPart;
        Comparison<T>[] comparisons;
        Predicate<T>[] predicates;
        int startIndex = 0;
        int stopIndex = int.MaxValue - 1;
        int count = -1;

        internal NodeEnumerator(Select select, DataProvider provider, WorkPart workPart)
            : this(select, provider, workPart, null, null)
        {

        }

        internal NodeEnumerator(Select select, DataProvider provider, WorkPart workPart, int startIndex, int stopIndex)
            : this(select, provider, workPart, null, null, startIndex, stopIndex)
        {
        }

        internal NodeEnumerator(Select select, DataProvider provider, WorkPart workPart,
            Predicate<T>[] predicates, Comparison<T>[] comparisons)
        {
            this.select = select;
            this.provider = provider;
            this.workPart = workPart;
            this.predicates = predicates;
            this.comparisons = comparisons;
        }

        internal NodeEnumerator(Select select, DataProvider provider, WorkPart workPart,
            Predicate<T>[] predicates, Comparison<T>[] comparisons, int startIndex, int stopIndex)
            : this(select, provider, workPart, predicates, comparisons)
        {
            this.startIndex = startIndex;
            this.stopIndex = stopIndex;
        }

        List<T> nodes = new List<T>();
        internal List<T> Nodes
        {
            get
            {
                return nodes;
            }
        }

        bool readerClosed = false;
        internal void Close()
        {
            if (!readerClosed)
            {
                if(reader!=null)
                    reader.Close();
                readerClosed = true;
                //=====================
                if (startIndex > 0)
                {
                    int lastIndex = nodes.Count - 1;
                    if (lastIndex < startIndex)
                        nodes.Clear();
                    else
                    {
                        for (int i = 0; i < startIndex; i++)
                            nodes.RemoveAt(0);
                    }
                }
            }
        }

        WorkNodeTable table;
        WorkNodeTable Table
        {
            get
            {
                if (provider == null || workPart == null)
                    return null;
                if (table == null)
                    table = provider.GetTable<WorkNodeTable>(workPart.WorkNode);
                return table;
            }
        }

        T CreateWorkNode()
        {
            T node = (T)Activator.CreateInstance(typeof(T));
            //=========================
            node.FullPath = workPart.Path + node.Path;
            node.WorkPart = workPart;
            //=========================
            FieldSet<WorkNode> fs = (FieldSet<WorkNode>)node;
            foreach (Column col in Table.Columns)
                fs[col.Name] = Reader[col.Name];
            //=========================
            ((IFieldSet)node).DefaultStatus = FieldSetStatus.Ready;
            return node;
        }

        IDataReader Reader {
            get {
                if (reader == null)
                    reader = select.ExecuteReader();
                return reader;
            }
        }

        internal Predicate<T>[] Predicates
        {
            get
            {
                return predicates;
            }
            set
            {
                predicates = value;
            }
        }

        internal Comparison<T>[] Comparisons
        {
            get
            {
                return comparisons;
            }
            set
            {
                comparisons = value;
            }
        }

        T current;
        #region IEnumerator<T> Members

        public T Current
        {
            get
            {
                return current;
            }
        }

        public bool IsClosed
        {
            get
            {
                return Reader.IsClosed;
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Close();
        }

        #endregion

        #region IEnumerator Members

        object System.Collections.IEnumerator.Current
        {
            get
            {
                return (object)Current;
            }
        }

        public bool MoveNext()
        {
            if (Reader.IsClosed)
                return false;
            if (count >= stopIndex && comparisons == null)
                goto FINAL;
            while (Reader.Read())
            {
                T obj = CreateWorkNode();
                if (!DoPredicate(obj))
                    continue;
                count++;
                //=====================
                Insert(obj);
                current = obj;
                //=====================
                return true;
            }
        FINAL:
            current = null;
            Close();
            return false;
        }

        public void Reset()
        {
            reader = null;
            current = null;
            count = -1;
        }

        #endregion

        bool DoPredicate(T obj)
        {
            if (predicates == null)
                return true;
            foreach (Predicate<T> p in predicates)
                if (!p(obj))
                    return false;
            return true;
        }

        void Insert(T node)
        {
            if (comparisons == null)
            {
                nodes.Add(node);
                return;
            }

            int lastIndex = nodes.Count - 1;
            int pos = 0;
            for (int i = lastIndex; i >= 0; i--)
            {
                //if node > nodes[i]then break
                if (DoComparison(node, nodes[i]))
                {
                    pos = i + 1;
                    break;
                }

                pos = i;
            }

            if (pos == lastIndex + 1)
            {
                if (lastIndex < stopIndex)
                    nodes.Add(node);
            }
            else
            {
                if (lastIndex >= stopIndex)
                    nodes.RemoveAt(lastIndex);
                if (pos == lastIndex)
                    nodes.Add(node);
                else
                    nodes.Insert(pos, node);
            }
        }

        bool DoComparison(T node1, T node2)
        {
            foreach (Comparison<T> comp in comparisons)
                if (comp(node1, node2) < 0)
                    return false;
            return true;
        }
    }
}
